UEditor(JSP版)使用总结 您所在的位置:网站首页 解决Ueditor编辑器上传附件附件图标地址暴露后台地址 UEditor(JSP版)使用总结

UEditor(JSP版)使用总结

2023-12-08 19:20| 来源: 网络整理| 查看: 265

1.背景

 公司一个后台模块要用到插入表格的功能,之前项目中使用的是 Umeditor,UEditor 的迷你版,然后悲催地发现 umeditor 中并没有插入表格的功能,好吧,只能换插件了。因为对百度富文本的风格较为熟悉,所以果断换完整版的 UEditor 了。于是开始了貌似短暂却充满艰辛的 ueditor 探(tong)索(ku)之路。

2.引入 Editor 插件(1)下载    链接一(ueditor 官方下载路径):http://ueditor.baidu.com/website/download.html    链接二(文档说明):http://fex.baidu.com/ueditor/ (2)引入到项目中,结构如下

 

 ① demo.html 文件是依据链接二中的文档说明新增的,其他均为插件中。

ueditor demo 这里写你的初始化内容 var ue = UE.getEditor('container');

此时,不需要修改任何东西,直接访问demo.html 就可以看到编辑器: 

3.实现图片上传

 一般情况下,现在其实功能都可以用了,只要保证你的路径配置是正确的,图片上传也没毛病。但是,可能你也会像我一样衰到上传图片几乎百度能搜到的几个常见问题基本一个不落地碰到,哭死……好吧,在看这种异常情况之前,我们先来了解几个重要的文件:

 ① ueditor.config.js

 serverUrl 为上传请求的统一的 controller

var URL = window.UEDITOR_HOME_URL || getUEBasePath(); // 服务器统一请求接口路径 serverUrl: URL + "jsp/controller.jsp"

 ② controller.jsp (ueditor\jsp\controller.jsp)

 jsp 为① 中配置的serverUrl 要请求的 controller(其中 system.out.println(rootPath) 我手动加的,测试用),这个时候要注意,需要导入 ueditor\jsp\lib 下的五个jar包,否则 ActionEnter 处会报错!!!

③ config.json(ueditor\jsp\config.json)

 上传的路径等的配置,要注意的是红框中的两项路径配置。可以修改为自己的路径。不正确时图片是无法显示的。

首先,当我引入完插件,并且照着大神们说的改好了路径配置之后满怀信心地点了下图片上传的图标,what?咋没反应?!!于是,F12,发现报错了:后台配置项返回格式出错,上传功能将不能正常使用!

浏览器显示酱紫:

  点击多图上传显示酱紫:

 

 这时候无知的我一开始看到配置项错了,以为是config.json 中的路径配置有问题,然后各种改啊,怎么都不好使啊啊啊!!在 controller.jsp 中的打印语句在控制台也没有打印啊,之后我直接在浏览器里去访问controller.jsp,然后你造么,根本没有解析有木有!!html全部原模原样打印出来了!!有问题,绝对有问题!于是我就开始怀疑 jsp 的位置在项目中被拦截了,于是去看项目的 spring 的配置,web.xml 配置,反正但凡是配置文件都看了个遍,可能自己太弱鸡了,并没看出来啥猫腻……但是笨人有笨办法啊,我跑去建了个 web 的 demo, 然后把 ueditor 拷了进去,在浏览器访问了下,图片竟然上传成功!!于是我带着无比激动的心情,直接访问controller.jsp,果然打印了一串 json 串,原来错误信息里说的配置项是这个 jsp 返回的 json 串啊!原来是这样,果然是 jsp 的存放的路径不对访问不到啊!!!至此,最大的问题找到了,解决就 so easy 了,ueditor 整个换了个可以访问到的位置就完美解决了,哈哈。至于其他几个错误都是在调试中出现的千奇百怪,还有图片不显示的,当然是配置的路径不对,这里就不多说了。

4.实现上传图片至 FTP

 做图片上传的时候发现 ueditor 图片是上传到了本地服务器,这样很不好啊,一是项目重新部署之后之前上传的图片会丢掉,二是我们的项目的前台页面是通过 ngnix 反向代理的,这样上传的图片根本显示不了。所以还是要上传到 ftp 的,但是百度了下发现 ueditor 是不支持上传 ftp 的,不过没关系,还是网上的大神们厉害。下面讲述一下改(chao)造(xi)的过程:

 ① 修改 controller.jsp 文件中引入的包:

 修改 import="com.baidu.ueditor.ActionEnter" 为 import="xx.xx.xx.ActionEnter",其中xx.xx.xx.ActionEnter 是我们接下来要自定义的类,没错,我们就是要改造 ueditor 实现上传的后台代码。

② config.json 中添加两项自定义配置,这两项配置我们会在后台去获取: "useFtpUpload": "true", /* 是否使用FTP上传 */ "keepLocalFile": "false", /* 使用FTP上传后本地服务器是否保存 */ 并修改 imageUrlPrefix 为 ftp 图片访问前缀。为了路径简单一点,imagePathFormat 我也进行了修改。  ③ 创建我们自己的ActionEnter 类: package com.itb.system.ueditorupload; import java.util.Map; import javax.servlet.http.HttpServletRequest; import com.baidu.ueditor.ConfigManager; import com.baidu.ueditor.define.ActionMap; import com.baidu.ueditor.define.AppInfo; import com.baidu.ueditor.define.BaseState; import com.baidu.ueditor.define.State; import com.baidu.ueditor.hunter.FileManager; import com.baidu.ueditor.hunter.ImageHunter; public class ActionEnter { private HttpServletRequest request = null; private String rootPath = null; private String contextPath = null; private String actionType = null; private ConfigManager configManager = null; public ActionEnter(HttpServletRequest request, String rootPath) { this.request = request; this.rootPath = rootPath; this.actionType = request.getParameter("action"); this.contextPath = request.getContextPath(); this.configManager = ConfigManager.getInstance(this.rootPath, this.contextPath, request.getRequestURI()); } public String exec() { String callbackName = this.request.getParameter("callback"); if (callbackName != null) { if (!validCallbackName(callbackName)) { return new BaseState(false, AppInfo.ILLEGAL).toJSONString(); } return callbackName + "(" + this.invoke() + ");"; } else { return this.invoke(); } } public String invoke() { if (actionType == null || !ActionMap.mapping.containsKey(actionType)) { return new BaseState(false, AppInfo.INVALID_ACTION).toJSONString(); } if (this.configManager == null || !this.configManager.valid()) { return new BaseState(false, AppInfo.CONFIG_ERROR).toJSONString(); } State state = null; int actionCode = ActionMap.getType(this.actionType); Map conf = null; switch (actionCode) { case ActionMap.CONFIG: return this.configManager.getAllConfig().toString(); case ActionMap.UPLOAD_IMAGE: conf = this.configManager.getConfig(actionCode); conf.put("useFtpUpload", this.configManager.getAllConfig().getString("useFtpUpload")); conf.put("keepLocalFile", this.configManager.getAllConfig().getString("keepLocalFile")); state = new Uploader(request, conf).doExec(); break; case ActionMap.UPLOAD_SCRAWL: case ActionMap.UPLOAD_VIDEO: case ActionMap.UPLOAD_FILE: conf = this.configManager.getConfig(actionCode); state = new Uploader(request, conf).doExec(); break; case ActionMap.CATCH_IMAGE: conf = configManager.getConfig(actionCode); String[] list = this.request.getParameterValues((String) conf.get("fieldName")); state = new ImageHunter(conf).capture(list); break; case ActionMap.LIST_IMAGE: case ActionMap.LIST_FILE: conf = configManager.getConfig(actionCode); int start = this.getStartIndex(); state = new FileManager(conf).listFile(start); break; } return state.toJSONString(); } public int getStartIndex() { String start = this.request.getParameter("start"); try { return Integer.parseInt(start); } catch (Exception e) { return 0; } } /** * callback参数验证 */ public boolean validCallbackName(String name) { if (name.matches("^[a-zA-Z_]+[\\w0-9_]*$")) { return true; } return false; } }

 ④ 模仿 ueditor 的 Uploader 类:

package com.itb.system.ueditorupload; import java.util.Map; import javax.servlet.http.HttpServletRequest; import com.baidu.ueditor.define.State; import com.baidu.ueditor.upload.Base64Uploader; import com.baidu.ueditor.upload.BinaryUploader; public class Uploader { private HttpServletRequest request = null; private Map conf = null; public Uploader(HttpServletRequest request, Map conf) { this.request = request; this.conf = conf; } public final State doExec() { String filedName = (String) this.conf.get("fieldName"); State state = null; if ("true".equals(this.conf.get("isBase64"))) { state = Base64Uploader.save(this.request.getParameter(filedName), this.conf); } else {//ftp上传判断 if("true".equals(this.conf.get("useFtpUpload"))){ state = FtpUploadUtilbaidu.save(request, conf); } else{ state = BinaryUploader.save(this.request, this.conf);//系统默认的上传方法 } } return state; } }

 ⑤ 新建 FtpUploadUtilbaidu类: 模仿 ueditor的 BinaryUploader 类 package com.itb.system.ueditorupload; import java.io.IOException; import java.io.InputStream; import java.util.Arrays; import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; import org.apache.commons.fileupload.FileItemIterator; import org.apache.commons.fileupload.FileItemStream; import org.apache.commons.fileupload.FileUploadException; import org.apache.commons.fileupload.disk.DiskFileItemFactory; import org.apache.commons.fileupload.servlet.ServletFileUpload; import com.baidu.ueditor.PathFormat; import com.baidu.ueditor.define.AppInfo; import com.baidu.ueditor.define.BaseState; import com.baidu.ueditor.define.FileType; import com.baidu.ueditor.define.State; public class FtpUploadUtilbaidu { public static final State save(HttpServletRequest request, Map conf) { FileItemStream fileStream = null; boolean isAjaxUpload = request.getHeader("X_Requested_With") != null; if (!ServletFileUpload.isMultipartContent(request)) { return new BaseState(false, AppInfo.NOT_MULTIPART_CONTENT); } ServletFileUpload upload = new ServletFileUpload(new DiskFileItemFactory()); if (isAjaxUpload) { upload.setHeaderEncoding("UTF-8"); } try { FileItemIterator iterator = upload.getItemIterator(request); while (iterator.hasNext()) { fileStream = iterator.next(); if (!fileStream.isFormField()) break; fileStream = null; } if (fileStream == null) { return new BaseState(false, AppInfo.NOTFOUND_UPLOAD_DATA); } String savePath = (String) conf.get("savePath"); String originFileName = fileStream.getName(); String suffix = FileType.getSuffixByFilename(originFileName); originFileName = originFileName.substring(0, originFileName.length() - suffix.length()); savePath = savePath + suffix; long maxSize = ((Long) conf.get("maxSize")).longValue(); if (!validType(suffix, (String[]) conf.get("allowFiles"))) { return new BaseState(false, AppInfo.NOT_ALLOW_FILE_TYPE); } savePath = PathFormat.parse(savePath, originFileName); String remoteDir = ""; String filename = savePath; int pos = savePath.lastIndexOf("/"); if (pos > -1) { remoteDir = savePath.substring(0, pos + 1); filename = savePath.substring(pos + 1); } String physicalPath = (String) conf.get("rootPath") + savePath; // 是否上传后保留本地服务器文件config.json,里面的配置 boolean keepLocalFile = "false".equals(conf.get("keepLocalFile")) ? false : true; InputStream is = fileStream.openStream(); State storageState = StorageManager.saveFtpFileByInputStream(is, remoteDir, physicalPath, maxSize, keepLocalFile, filename); is.close(); if (storageState.isSuccess()) { storageState.putInfo("url", PathFormat.format(savePath)); storageState.putInfo("type", suffix); storageState.putInfo("original", originFileName + suffix); storageState.putInfo("title", filename); } return storageState; } catch (FileUploadException e) { return new BaseState(false, AppInfo.PARSE_REQUEST_ERROR); } catch (IOException e) { } return new BaseState(false, AppInfo.IO_ERROR); } private static boolean validType(String type, String[] allowTypes) { List list = Arrays.asList(allowTypes); return list.contains(type); } }  ⑥ 模仿 ueditor 的  StorageManager 类,其中,Config.get("ftp.ip") 等是获取 config 文件中配置的ftp各配置项,此处不赘述。 package com.itb.system.ueditorupload; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import org.apache.commons.io.FileUtils; import com.baidu.ueditor.define.AppInfo; import com.baidu.ueditor.define.BaseState; import com.baidu.ueditor.define.State; import com.itb.data.util.Config; public class StorageManager { public static final int BUFFER_SIZE = 8192; private static String host = Config.get("ftp.ip"); // host地址 private static int port = Integer.parseInt(Config.get("ftp.port")); // 端口 private static String username = Config.get("ftp.username"); // 用户名 private static String password = Config.get("ftp.password"); // 密码 private String ftpURL = Config.get("ftp.url");// ftp服务器的访问路径 图片服务器 public interface SaveListener { State save(byte[] data, String fileName); } public static SaveListener saveListener; public void setSaveListener(SaveListener saveListener) { StorageManager.saveListener = saveListener; } public StorageManager() { } public static State saveBinaryFile(byte[] data, String path) { if (saveListener != null) { return saveListener.save(data, path.substring(path.lastIndexOf("/") + 1)); } File file = new File(path); State state = valid(file); if (!state.isSuccess()) { return state; } try { BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file)); bos.write(data); bos.flush(); bos.close(); } catch (IOException ioe) { return new BaseState(false, AppInfo.IO_ERROR); } state = new BaseState(true, file.getAbsolutePath()); state.putInfo("size", data.length); state.putInfo("title", file.getName()); return state; } public static State saveFileByInputStream(InputStream is, String path, long maxSize) { State state = null; File tmpFile = getTmpFile(); byte[] dataBuf = new byte[2048]; BufferedInputStream bis = new BufferedInputStream(is, StorageManager.BUFFER_SIZE); try { BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(tmpFile), StorageManager.BUFFER_SIZE); int count = 0; while ((count = bis.read(dataBuf)) != -1) { bos.write(dataBuf, 0, count); } bos.flush(); bos.close(); if (tmpFile.length() > maxSize) { tmpFile.delete(); return new BaseState(false, AppInfo.MAX_SIZE); } if (saveListener != null) { byte[] bs = File2byte(tmpFile); tmpFile.delete(); return saveListener.save(bs, path.substring(path.lastIndexOf("/") + 1)); } state = saveTmpFile(tmpFile, path); if (!state.isSuccess()) { tmpFile.delete(); } return state; } catch (IOException e) { } return new BaseState(false, AppInfo.IO_ERROR); } public static State saveFileByInputStream(InputStream is, String path) { State state = null; File tmpFile = getTmpFile(); byte[] dataBuf = new byte[2048]; BufferedInputStream bis = new BufferedInputStream(is, StorageManager.BUFFER_SIZE); try { BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(tmpFile), StorageManager.BUFFER_SIZE); int count = 0; while ((count = bis.read(dataBuf)) != -1) { bos.write(dataBuf, 0, count); } bos.flush(); bos.close(); if (saveListener != null) { byte[] bs = File2byte(tmpFile); tmpFile.delete(); return saveListener.save(bs, path.substring(path.lastIndexOf("/") + 1)); } state = saveTmpFile(tmpFile, path); if (!state.isSuccess()) { tmpFile.delete(); } return state; } catch (IOException e) { } return new BaseState(false, AppInfo.IO_ERROR); } private static File getTmpFile() { File tmpDir = FileUtils.getTempDirectory(); String tmpFileName = (Math.random() * 10000 + "").replace(".", ""); return new File(tmpDir, tmpFileName); } private static State saveTmpFile(File tmpFile, String path) { State state = null; File targetFile = new File(path); if (targetFile.canWrite()) { return new BaseState(false, AppInfo.PERMISSION_DENIED); } try { FileUtils.moveFile(tmpFile, targetFile); } catch (IOException e) { return new BaseState(false, AppInfo.IO_ERROR); } state = new BaseState(true); state.putInfo("size", targetFile.length()); state.putInfo("title", targetFile.getName()); return state; } private static State valid(File file) { File parentPath = file.getParentFile(); if ((!parentPath.exists()) && (!parentPath.mkdirs())) { return new BaseState(false, AppInfo.FAILED_CREATE_FILE); } if (!parentPath.canWrite()) { return new BaseState(false, AppInfo.PERMISSION_DENIED); } return new BaseState(true); } private static byte[] File2byte(File file) { byte[] buffer = null; try { FileInputStream fis = new FileInputStream(file); ByteArrayOutputStream bos = new ByteArrayOutputStream(); byte[] b = new byte[1024]; int n; while ((n = fis.read(b)) != -1) { bos.write(b, 0, n); } fis.close(); bos.close(); buffer = bos.toByteArray(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return buffer; } /** * 上传FTP文件 * * @param is * @param path * @param maxSize * @return */ public static State saveFtpFileByInputStream(InputStream is, String remoteDir, String path, long maxSize, boolean keepLocalFile, String filename) { State state = null; File tmpFile = getTmpFile(); byte[] dataBuf = new byte[2048]; BufferedInputStream bis = new BufferedInputStream(is, StorageManager.BUFFER_SIZE); try { BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(tmpFile), StorageManager.BUFFER_SIZE); int count = 0; while ((count = bis.read(dataBuf)) != -1) { bos.write(dataBuf, 0, count); } bos.flush(); bos.close(); if (tmpFile.length() > maxSize) { tmpFile.delete(); return new BaseState(false, AppInfo.MAX_SIZE); } try { FileInputStream in = new FileInputStream(tmpFile); FTPUtil.uploadFile(host, port, username, password, "", remoteDir, filename, in); } catch (Exception e) { tmpFile.delete(); return new BaseState(false, AppInfo.IO_ERROR); } state = new BaseState(true); state.putInfo("size", tmpFile.length()); if (keepLocalFile) { File targetFile = new File(path); if (targetFile.canWrite()) { return new BaseState(false, AppInfo.PERMISSION_DENIED); } try { FileUtils.moveFile(tmpFile, targetFile); } catch (IOException e) { tmpFile.delete(); return new BaseState(false, AppInfo.IO_ERROR); } } tmpFile.delete(); return state; } catch (IOException localIOException) { tmpFile.delete(); } return new BaseState(false, AppInfo.IO_ERROR); } }  ⑦ 新建 FTP 上传下载工具类  FTPUtil package com.itb.system.ueditorupload; import java.io.IOException; import java.io.InputStream; import org.apache.commons.net.ftp.FTP; import org.apache.commons.net.ftp.FTPClient; import org.apache.commons.net.ftp.FTPClientConfig; import org.apache.commons.net.ftp.FTPReply; /** * ftp上传下载工具类 */ public class FTPUtil { /** * Description: 向FTP服务器上传文件 * * @param host * FTP服务器hostname * @param port * FTP服务器端口 * @param username * FTP登录账号 * @param password * FTP登录密码 * @param basePath * FTP服务器基础目录 * @param filePath * FTP服务器文件存放路径。例如分日期存放:/20150101。文件的路径为basePath+filePath * @param filename * 上传到FTP服务器上的文件名 * @param input * 输入流 * @return 成功返回true,否则返回false */ public static boolean uploadFile(String host, int port, String username, String password, String basePath, String filePath, String filename, InputStream input) { boolean result = false; FTPClient ftp = new FTPClient(); try { int reply; ftp.connect(host, port);// 连接FTP服务器 // 如果采用默认端口,可以使用ftp.connect(host)的方式直接连接FTP服务器 ftp.login(username, password);// 登录 reply = ftp.getReplyCode(); if (!FTPReply.isPositiveCompletion(reply)) { ftp.disconnect(); return result; } // 切换到上传目录 if (!ftp.changeWorkingDirectory(basePath + filePath)) { // 如果目录不存在创建目录 String[] dirs = filePath.split("/"); String tempPath = basePath; for (String dir : dirs) { if (null == dir || "".equals(dir)) continue; tempPath += "/" + dir; if (!ftp.changeWorkingDirectory(tempPath)) { if (!ftp.makeDirectory(tempPath)) { return result; } else { ftp.changeWorkingDirectory(tempPath); } } } } ftp.setControlEncoding("ISO-8859-1");// 设置编码集为GBK支持中文 FTPClientConfig conf = new FTPClientConfig(FTPClientConfig.SYST_UNIX); conf.setServerLanguageCode("zh"); ftp.configure(conf); // 设置上传文件的类型为二进制类型 ftp.setFileType(FTP.BINARY_FILE_TYPE); ftp.enterLocalPassiveMode();// 设为被动模式 filename = new String(filename.getBytes("GBK"), "ISO-8859-1"); String[] strs = ftp.listNames(); boolean b = true; for (String str : strs) { if (str != null && str.equals(filename)) { b = false; break; } } if (b) { // 上传文件 if (!ftp.storeFile(filename, input)) { return result; } } input.close(); ftp.logout(); result = true; } catch (IOException e) { e.printStackTrace(); } finally { if (ftp.isConnected()) { try { ftp.disconnect(); } catch (IOException ioe) { } } } return result; } }  至此,UEditor 实现上传图片至 FTP 就实现了,终于有了一种如释重负的感觉,在这里特别感谢这些大神们的分享。 5.整合 easyUI 时的层级及其他问题

 公司后台用的前端框架为 easyUI,把 ueditor 放进去之后发现,咦,ueditor的所有弹框怎么跑到下面了呢?还有在ueditor 中编辑一次关闭 easyUI弹框怎么打开之后还是上次编辑的内容呢?第一个问题太复杂不适合我,于是果断甩给了前端小伙伴,然后他成功解决之后我就坐享其成了,哈哈。

 在此,记录一下解决方法:其中 id 为 dlg 的为要使用 ueditor 编辑器的 easyUI的弹框。 $('#dlg').window({ onBeforeClose:function(){ UE.getEditor('body').destroy();//关闭 easyUI 弹框时销毁当前编辑器,打开时再重新初始化。 }, onOpen:function(){//以下三个方法为设置层级 $(".panel").css("z-index","999"); $(".window-shadow").css("z-index","998"); }, onMove:function(left,top){ $(".panel").css("z-index","999"); $(".window-shadow").css("z-index","998"); }, onResize:function(){ $(".panel").css("z-index","999"); $(".window-shadow").css("z-index","998"); } }); 6.在 easyUI 中使用 UEditor 编辑器 最后,附上 ueditor 自选配置项初始化编辑器的方法: ①表单中: ②初始化方法,可按照需要自己配置(打开弹框时调用即可):

 可使用默认配置:

function initEditor(){ var ue = UE.getEditor('body'); }  也可以自定义配置: function initEditor1(){ var ue1 = UE.getEditor('body1',{ initialFrameWidth : 660, // 初始化编辑器宽度,默认640 initialFrameHeight:320, // 初始化编辑器高度,默认200 autourldetectinie : false,// ie下的链接自动监测 lang:"zh-cn",// 语言配置项,默认是zh-cn charset : "utf-8",// 针对getAllHtml方法,会在对应的head标签中增加该编码设置。 textarea : 'body1', // 提交表单时,服务器获取编辑器提交内容的所用的参数,多实例时可以给容器name属性,会将name给定的值最为每个实例的键值,不用每次实例化的时候都设置这个值 emotionLocalization : true,// 是否开启表情本地化,默认关闭。若要开启请确保emotion文件夹下包含官网提供的images表情文件夹 allHtmlEnabled : false, // 提交到后台的数据是否包含整个html字符串 autoHeightEnabled:false, //关闭字数统计 wordCount:false, //关闭elementPath elementPathEnabled:false //工具栏上的所有的功能按钮和下拉框,可以在new编辑器的实例时选择自己需要的重新定义,可在ueditor.config.js中查看全部功能 , toolbars: [[ 'fullscreen', 'source', '|', 'undo', 'redo', '|', 'bold', 'italic', 'underline', 'fontborder', 'strikethrough', 'superscript', 'subscript', 'removeformat', 'formatmatch', 'autotypeset', 'blockquote', 'pasteplain', '|', 'forecolor', 'backcolor', 'insertorderedlist', 'insertunorderedlist', 'selectall', 'cleardoc', '|', 'rowspacingtop', 'rowspacingbottom', 'lineheight', '|', 'customstyle', 'paragraph', 'fontfamily', 'fontsize', '|', 'directionalityltr', 'directionalityrtl', 'indent', '|', 'justifyleft', 'justifycenter', 'justifyright', 'justifyjustify', '|', 'touppercase', 'tolowercase', '|', 'horizontal', 'date', 'time', 'spechars', '|', 'inserttable', 'deletetable', 'insertparagraphbeforetable', 'insertrow', 'deleterow', 'insertcol', 'deletecol', 'mergecells', 'mergeright', 'mergedown', 'splittocells', 'splittorows', 'splittocols', 'charts', '|', 'preview', 'searchreplace', 'drafts' ]] }); }


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有